using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    public enum ALUType
    {
        Add,
        Inv,
        TwoComplement,
        AndMask,
        OrMask,
        XorMask,
        CompareGreater,
        CompareEqual,
        Subtract,
        AddSub,
        ShiftRightArith,
        ShiftLeftArith,
        ShiftRightLogical,
        ShiftLeftLogical,
        ShiftRightRotate,
        ShiftLeftRotate,
        Inc,
        Dec,
        OrReduction,
        AndReduction,
        Error,
        //PadZero,
        //Subset,
        Merge
    }
    
    public class ALUComponent : Component
    {
        public NodeVector I_A;
        public NodeVector I_B;

        public NodeVector I_Subtract;

        public NodeVector O_A;

        ALUType eType;
        int iWidth=0;
        int iParamA=0;
        int iParamB=0;
        bool bShiftLeft=false;
        bool bTypeAdd=false;
        bool bTypeShift=false;
        bool bTypeLogic=false;
        bool bTypeCompare=false;
        bool bSingleInput = false;
        bool bBitOutput = false;
        bool bTypeReForm = false;

        public ALUComponent(Component poParentComponent, string psName, ALUType peType, int piInputWidth)
            : base(poParentComponent, psName)
        {
            iWidth = piInputWidth;
            eType = peType;
            iParamA = 0;
            InitNodeVectors();
        }

        public ALUComponent(Component poParentComponent, string psName, ALUType peType, int piInputWidth, int piParamA)
            : base(poParentComponent, psName)
        {
            iWidth = piInputWidth;
            eType = peType;
            iParamA = piParamA;
            InitNodeVectors();
        }

        private void InitNodeVectors()
        {
            switch (eType)
            {
                case ALUType.Add:
                    bTypeAdd = true;
                    break;
                case ALUType.Inv:
                    bTypeLogic = true;
                    bSingleInput = true;
                    break;
                case ALUType.TwoComplement:
                    bTypeAdd = true;
                    bSingleInput = true;
                    break;
                case ALUType.AndMask:
                    bTypeLogic = true;
                    break;
                case ALUType.OrMask:
                    bTypeLogic = true;
                    break;
                case ALUType.XorMask:
                    bTypeLogic = true;
                    break;
                case ALUType.CompareGreater:
                    bTypeCompare = true;
                    bBitOutput = true;
                    break;
                case ALUType.CompareEqual:
                    bTypeCompare = true;
                    bBitOutput = true;
                    break;
                case ALUType.Subtract:
                    bTypeAdd = true;
                    break;
                case ALUType.AddSub:
                    bTypeAdd = true;
                    break;
                case ALUType.ShiftRightArith:
                    bTypeShift = true;
                    bSingleInput = true;
                    break;
                case ALUType.ShiftLeftArith:
                    bTypeShift = true;
                    bSingleInput = true;
                    bShiftLeft = true;
                    break;
                case ALUType.ShiftRightLogical:
                    bTypeShift = true;
                    bSingleInput = true;
                    break;
                case ALUType.ShiftLeftLogical:
                    bTypeShift = true;
                    bSingleInput = true;
                    bShiftLeft = true;
                    break;
                case ALUType.ShiftRightRotate:
                    bTypeShift = true;
                    bSingleInput = true;
                    break;
                case ALUType.ShiftLeftRotate:
                    bTypeShift = true;
                    bSingleInput = true;
                    bShiftLeft = true;
                    break;
                case ALUType.Inc:
                    bTypeAdd = true;
                    bSingleInput = true;
                    break;
                case ALUType.Dec:
                    bTypeAdd = true;
                    bSingleInput = true;
                    break;
                case ALUType.OrReduction:
                    bSingleInput = true;
                    bTypeLogic = true;
                    break;
                case ALUType.AndReduction:
                    bSingleInput = true;
                    bTypeLogic = true;
                    break;
                case ALUType.Merge:
                    bTypeLogic = true;
                    break;
                default:
                    break;
            }

            if (bTypeShift)
            {
                if (iParamA <= 0)
                    iParamA = 1;
                else
                    if (iParamA > iWidth)
                        throw new Exception("ALU shift width is greater than input width");
            }

            I_A = this.CreateNodeVector("A_I", iWidth);
            if (!bSingleInput)
            {
                if (eType != ALUType.Merge)
                {
                    I_B = this.CreateNodeVector("B_I", iWidth);
                }
                else
                {
                    I_B = this.CreateNodeVector("B_I",iParamA);
                }
            }
            else
                I_B = null;
            if (bBitOutput)
                O_A = this.CreateNodeVector("A_O", 1);
            else
            {
                if (bTypeAdd)
                    O_A = this.CreateNodeVector("A_O", iWidth + 1);
                else
                    if (eType == ALUType.Merge)
                        O_A = this.CreateNodeVector("A_O", iWidth + iParamA);
                    else
                        O_A = this.CreateNodeVector("A_O", iWidth);
            }

            if (eType == ALUType.AddSub)
            {
                I_Subtract = this.CreateNodeVector("SUB_I", 1);
            }
            else
            {
                I_Subtract = null;
            }
        }

        public override void GenerateStructure()
        {
            if (bTypeAdd)
            {
                GenerateAdder();
            }

            if (bTypeCompare)
            {
                GenerateCompare();
            }

            if (bTypeShift)
            {
                GenerateShift();
            }

            if (bTypeLogic)
            {
                GenerateLogic();
            }
        }

        private void GenerateAdder()
        {
            Adder oAdder = new Adder(this, "adder", iWidth, iWidth, true);
            O_A.ConnectSubset(0, iWidth - 1, oAdder.OutputNodes, 0, iWidth - 1);
            if (eType == ALUType.TwoComplement)
            {
                oAdder.InputNodesA.Join(!I_A);
                oAdder.InputNodesB.Join(0);
            }
            else
            {
                oAdder.InputNodesA.Join(I_A);
            }


            if (eType == ALUType.Subtract || eType == ALUType.Dec || eType == ALUType.AddSub || eType == ALUType.TwoComplement)
            {
                if (eType == ALUType.AddSub)
                {
                    Xor oXor = new Xor(this, "carry_out");
                    oXor.InputNodes.ConnectSubset(0, 0, oAdder.OutputNodes, iWidth, iWidth);
                    oXor.InputNodes.ConnectSubset(1, 1, I_Subtract, 0, 0);
                    O_A.ConnectSubset(iWidth, iWidth, oXor.OutputNodes, 0, 0);
                    oAdder.Carry.Join(I_Subtract);
                }
                else
                {
                    O_A.Subset(this, iWidth, iWidth).Join(!oAdder.OutputNodes.Subset(this, iWidth, iWidth));
                    oAdder.Carry.Join(1);
                }

            }
            else
            {
                oAdder.Carry.Join(0);
                O_A.Subset(this, iWidth, iWidth).Join(oAdder.OutputNodes.Subset(this, iWidth, iWidth));
            }

            if (eType == ALUType.Add)
            {
                oAdder.InputNodesB.Join(I_B);
            }

            if (eType == ALUType.Subtract)
            {
                oAdder.InputNodesB.Join(!I_B);
            }

            if (eType == ALUType.Dec || eType == ALUType.Inc)
            {
                Constant oConst = new Constant(this,"const_one",iWidth,1);
                if (eType == ALUType.Dec)
                    oAdder.InputNodesB.Join(!oConst.OutputNodes);
                else
                    oAdder.InputNodesB.Join(oConst.OutputNodes);
            }

            if (eType == ALUType.AddSub)
            {
                Mux oMux = new Mux(this, "add_sub_mux", iWidth, 2);
                oMux.InputNodes[0].Join(I_B);
                oMux.InputNodes[1].Join(!I_B);
                oMux.SelectNodes.Join(I_Subtract);
                oAdder.InputNodesB.Join(oMux.OutputNodes);
            }
        }

        private void GenerateCompare()
        {
            if (eType == ALUType.CompareGreater)
            {
                CompareGreaterEqual oCompare = new CompareGreaterEqual(this, "compare_greater", iWidth, iWidth);
                oCompare.InputNodesA.Join(I_A);
                oCompare.InputNodesB.Join(I_B);
                oCompare.Output.Join(O_A);
            }

            if (eType == ALUType.CompareEqual)
            {
                CompareEqual oCompare = new CompareEqual(this, "compare_equal", iWidth, iWidth);
                oCompare.InputNodesA.Join(I_A);
                oCompare.InputNodesB.Join(I_B);
                oCompare.Output.Join(O_A);
            }
        }

        private void GenerateLogic()
        {
            if (eType == ALUType.AndMask)
            {
                for (int iBit = 0; iBit < iWidth; iBit++)
                {
                    AndGate oGate = new AndGate(this, "and_" + iBit.ToString(), 2);
                    oGate.InputNodes.ConnectSubset(0, 0, I_A, iBit, iBit);
                    oGate.InputNodes.ConnectSubset(1, 1, I_B, iBit, iBit);
                    oGate.OutputNodes.ConnectSubset(O_A, iBit, iBit);
                }
            }

            if (eType == ALUType.OrMask)
            {
                for (int iBit = 0; iBit < iWidth; iBit++)
                {
                    OrGate oGate = new OrGate(this, "or_" + iBit.ToString(), 2);
                    oGate.InputNodes.ConnectSubset(0, 0, I_A, iBit, iBit);
                    oGate.InputNodes.ConnectSubset(1, 1, I_B, iBit, iBit);
                    oGate.OutputNodes.ConnectSubset(O_A, iBit, iBit);
                }
            }

            if (eType == ALUType.XorMask)
            {
                for (int iBit = 0; iBit < iWidth; iBit++)
                {
                    Xor oGate = new Xor(this, "or_" + iBit.ToString());
                    oGate.InputNodes.ConnectSubset(0, 0, I_A, iBit, iBit);
                    oGate.InputNodes.ConnectSubset(1, 1, I_B, iBit, iBit);
                    oGate.OutputNodes.ConnectSubset(O_A, iBit, iBit);
                }
            }

            if (eType == ALUType.Inv)
            {
                O_A.Join(!I_A);
            }

            if (eType == ALUType.OrReduction)
            {
                O_A.Join(Operators.OR(this,I_A));
            }

            if (eType == ALUType.AndReduction)
            {
                O_A.Join(Operators.AND(this, I_A));
            }

            if (eType == ALUType.Merge)
            {
                O_A[O_A.Width - I_A.Width, O_A.Width].Join(I_A);
                O_A[0, O_A.Width - I_A.Width - 1].Join(I_B);
            }
        }

        private void GenerateShift()
        {
            int iSourceBit;

            NodeVector oConstZero = null;
            if (eType == ALUType.ShiftLeftLogical || eType == ALUType.ShiftRightLogical)
            {
                oConstZero = this.CreateNodeVector("zero_bit",1);
                Constant oConstant = new Constant(this, "zero", 1, 0);
                oConstZero.Join(oConstant.OutputNodes);
            }

            for (int iBit = 0; iBit < iWidth; iBit++)
            {
                if (bShiftLeft)
                {
                    iSourceBit = iBit - iParamA;
                    if (iSourceBit < 0)
                    {
                        if (eType == ALUType.ShiftLeftArith)
                        {
                            iSourceBit = 0;
                        }
                        if (eType == ALUType.ShiftLeftRotate)
                        {
                            iSourceBit += iWidth;
                        }
                        if (eType == ALUType.ShiftLeftLogical)
                        {
                            iSourceBit = -1;
                        }
                    }
                }
                else
                {
                    iSourceBit = iBit + iParamA;
                    if (iSourceBit >= iWidth)
                    {
                        if (eType == ALUType.ShiftRightArith)
                        {
                            iSourceBit = iWidth - 1;
                        }
                        if (eType == ALUType.ShiftRightRotate)
                        {
                            iSourceBit -= iWidth;
                        }
                        if (eType == ALUType.ShiftRightLogical)
                        {
                            iSourceBit = -1;
                        }                        
                    }
                }
                if (iSourceBit == -1)
                {
                    O_A.ConnectSubset(iBit, iBit, oConstZero, 0, 0);
                }
                else
                {
                    O_A.ConnectSubset(iBit, iBit, I_A ,iSourceBit,iSourceBit);
                }
            }
        }

        public static void SizeIO(ALUType peALUType, int piWidth, int piParamA, out int piWidthIn1, out int piWidthOut)
        {
            int iA;
            int iB;
            int iO;
            switch (peALUType)
            {
                case ALUType.Add:
                    iA = piWidth;
                    iB = iA;
                    iO = piWidth + 1;
                    break;
                case ALUType.Inv:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.TwoComplement:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.AndMask:
                    iA = piWidth;
                    iB = iA;
                    iO = piWidth;
                    break;
                case ALUType.OrMask:
                    iA = piWidth;
                    iB = iA;
                    iO = piWidth;
                    break;
                case ALUType.XorMask:
                    iA = piWidth;
                    iB = iA;
                    iO = piWidth;
                    break;
                case ALUType.CompareGreater:
                    iA = piWidth;
                    iB = iA;
                    iO = 1;
                    break;
                case ALUType.CompareEqual:
                    iA = piWidth;
                    iB = iA;
                    iO = 1;
                    break;
                case ALUType.Subtract:
                    iA = piWidth;
                    iB = iA;
                    iO = piWidth + 1;
                    break;
                case ALUType.AddSub:
                    iA = piWidth;
                    iB = iA;
                    iO = piWidth + 1;
                    break;
                case ALUType.ShiftRightArith:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.ShiftLeftArith:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.ShiftRightLogical:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.ShiftLeftLogical:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.ShiftRightRotate:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.ShiftLeftRotate:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;
                    break;
                case ALUType.Inc:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth + 1;
                    break;
                case ALUType.Dec:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth + 1;
                    break;
                case ALUType.OrReduction:
                    iA = piWidth;
                    iB = 0;
                    iO = 1;
                    break;
                case ALUType.AndReduction:
                    iA = piWidth;
                    iB = 0;
                    iO = 1;
                    break;
                /*case ALUType.Subset:
                    iA = piWidth;
                    iB = 0;
                    iO = piParamA - piParamB;
                    if (iO < 0)
                        iO *= -1;
                    iO++;
                case ALUType.PadZero:
                    iA = piWidth;
                    iB = 0;
                    iO = piWidth;*/
                case ALUType.Merge:
                    iA = piWidth;
                    iB = piParamA;
                    iO = iA + iB;
                    break;
                default:
                    iA = 0;
                    iB = 0;
                    iO = 0;
                    break;
            }
            piWidthIn1 = iA;
            piWidthOut = iO;
        }

        public static ALUType ALUTypeFromString(string psOperation)
        {
            switch (psOperation.ToLower())
            {
                case "+":
                    return ALUType.Add;
                    break;
                case "!":
                    return ALUType.Inv;
                    break;
                case "2!":
                    return ALUType.TwoComplement;
                    break;
                case "&":
                    return ALUType.AndMask;
                    break;
                case "|":
                    return ALUType.OrMask;
                    break;
                case "x":
                    return ALUType.XorMask;
                    break;
                case ">":
                    return ALUType.CompareGreater;
                    break;
                case "=":
                    return ALUType.CompareEqual;
                    break;
                case "==":
                    return ALUType.CompareEqual;
                    break;
                case "-":
                    return ALUType.Subtract;
                    break;
                case ">>a":
                    return ALUType.ShiftRightArith;
                    break;
                case "<<a":
                    return ALUType.ShiftLeftArith;
                    break;
                case ">>l":
                    return ALUType.ShiftRightLogical;
                    break;
                case "<<l":
                    return ALUType.ShiftLeftLogical;
                    break;
                case ">>r":
                    return ALUType.ShiftRightRotate;
                    break;
                case "<<r":
                    return ALUType.ShiftLeftRotate;
                    break;
                case "U+":
                    return ALUType.Inc;
                    break;
                case "U-":
                    return ALUType.Dec;
                    break;
                case "U|":
                    return ALUType.OrReduction;
                    break;
                case "U&":
                    return ALUType.AndReduction;
                    break;
                case "<->":
                    return ALUType.Merge;
                default:
                    return ALUType.Error;
            }

        }

    }
}
